#include <iostream>
#include "Directory/Directory.h"
#include "DateTime/DateTime.h"
#include "Log.h"

Log* Log::m_Instance = NULL;

Log::Lock Log::m_Mutex = new std::mutex();

// Get the instance of log
Log* Log::GetInstance()
{
	if (m_Instance == NULL)
	{
		std::lock_guard<std::mutex> locker(*m_Mutex);

		if (m_Instance == NULL)
		{
			Log* log = new Log();

			m_Instance = log;
		}
	}

	return m_Instance;
}

// Destory log self
Log::None Log::DestoryInstance()
{
	if (m_Instance)
	{
		delete m_Instance;

		m_Instance = NULL;
	}
}

// Construct the log
Log::Log() :
	m_LogFileName(""),
	m_LogDirectory(""),
	m_iMaxLogSize(MAX_FILE_SIZE),
	m_iLogCountOneTime(MAX_MSG_NUM),
	m_bDisposed(false)
{
	Initialize();
}

// Destory the log
Log::~Log()
{
	Destory();
}

// Init the log
Log::None Log::Initialize()
{
	std::string strCurExePath = SystemTool::IO::Directory::GetExcutableDirectory();

	strCurExePath = SystemTool::IO::Directory::AddEnding(strCurExePath) + "SystemLog";

	ConfigureEnvironment(strCurExePath);

	RegisterLogType();
}

// Dispose the resource of log
Log::None Log::Destory()
{
	if (!GetDisposed())
	{
		SetDisposed(true);

		Flush();
	}
}

// Register the log type
Log::None Log::RegisterLogType()
{
	LogTypeArray[LOG_INFOR] = "LOG_INFO";

	LogTypeArray[LOG_WARN] = "LOG_WARN";

	LogTypeArray[LOG_DEBUG] = "LOG_DEBUG";

	LogTypeArray[LOG_ERROR] = "LOG_ERROR";
}

// Add seperate lines to log file
Log::BOOL Log::AddSeperateLines(std::string strLogFileName)
{
	if (strLogFileName.empty())
	{
		return false;
	}

	File FileHelper;

	if (!FileHelper.Open(strLogFileName, OpenMode::FILE_APPEND))
	{
		return false;
	}

	FileHelper.Seek(SeekOrigin::FILE_SEEK_END, 0);

	std::string strSepData = "\r\n\r\n";

	FileHelper.Write(strSepData.c_str(), static_cast<int>(strSepData.length()));

	FileHelper.Close();

	return true;
}

// Log the message into file
Log::None Log::LogToFile(std::string& strLogFileName, std::string& strLogMessage)
{
	if (strLogFileName.empty() || strLogMessage.empty())
	{
		return;
	}

	File FileHelper;

	FileHelper.Open(strLogFileName, OpenMode::FILE_APPEND);

	FileHelper.Seek(SeekOrigin::FILE_SEEK_END, 0);

	int uLen = static_cast<int>(strLogMessage.length());

	FileHelper.Write(strLogMessage.c_str(), uLen);

	FileHelper.Close();
}

// Get the current time
std::string Log::LocalTime()
{
	return DateTime::Now().ToString(DATE_REGION_UTC);
}

// Create log directory
Log::BOOL Log::CreateLogDirectory()
{
	if (GetLogDirectory().empty())
	{
		std::cerr << "Please configure the log environment at first with interface: ConfigureEnvironment" << std::endl;

		return false;
	}

	if (!Directory::IsExisted(GetLogDirectory()))
	{
		// Create a directory
		if (!Directory::Create(GetLogDirectory(), true))
		{
			return false;
		}
	}

	return true;
}

// Create log file
Log::BOOL Log::CreateLogFile()
{
	// Create log file name
	if (GetFileName().empty())
	{
		SetFileName(CreateFileName());
	}

	File FileHelper;

	if (!FileHelper.IsExisted(GetFileName()))
	{
		if (!FileHelper.Create(GetFileName()))
		{
			return false;
		}
	}

	return true;
}

// Create a new file name
std::string Log::CreateFileName()
{
	// Organize the new file name
	std::string strRootDir = Directory::AddEnding(GetLogDirectory());

	std::string strFileName = strRootDir + "SystemLog" + ".log";

	return strFileName;
}

// Get current log file size
Log::Size Log::GetCurLogFileSize()
{
	Size iCurFileSize = File::GetFileSize(GetFileName());

	return iCurFileSize;
}

// Write the message to log
Log::None Log::WriteLog()
{
	// Create a log directory
	if (!CreateLogDirectory())
	{
		return;
	}

	// Create a log file
	if (!CreateLogFile())
	{
		std::cerr << "Create a log file failed!" << std::endl;

		return;
	}

	// Connect all std::string together 
	std::string strNewMessage = ConnectAllMessages();

	// Clear all messages in log buffer
	Clear();

	// Get the current file's size
	Size iCurFileSize = GetCurLogFileSize();

	// Compare the size with preSet fileSize
	Size leftSize = GetMaxLogSize() - iCurFileSize;

	Size iMsgLen = strNewMessage.length();

	if (leftSize < iMsgLen)
	{
		// Backup the old log
		BackupLog(GetFileName());

		// Create a new file name
		SetFileName(CreateFileName());

		// Log the message to the new file
		LogToFile(GetFileName(), strNewMessage);

		return;
	}

	// Write the connected message to the file
	LogToFile(GetFileName(), strNewMessage);
}

// Bacckup log file
Log::None Log::BackupLog(std::string strLogFileName)
{
	// Create a new file name
	int iPos = strLogFileName.find_last_of('\\');

	if (iPos == -1)
	{
		return;
	}

	std::string strLogDir = strLogFileName.substr(0, iPos + 1);

	std::string strLogName = strLogFileName.substr(iPos + 1);

	int iPos1 = strLogName.find_last_of('.');

	if (iPos1 == -1)
	{
		return;
	}

	std::string strLog = strLogName.substr(0, iPos1);

	std::string strNewLogFileName = strLogDir
		+ strLog
		+ "_"
		+ LocalTime()
		+ ".log";

	// Rename the log file
	File::Rename(strLogFileName, strNewLogFileName);

	// Create a new file with old name
	File FileHelper;

	FileHelper.Create(strLogFileName);

	FileHelper.Close();
}

// Build a full log message
std::string Log::BuildMessage(LogType eLogType,
	ProcessID strProcessID,
	ThreadID strThreadID,
	std::string strFileName,
	LineNumber iLineNo,
	std::string strFuncName,
	std::string strMessage,
	std::string strRemark)
{
	// Get the time
	std::string strCurTime = DateTime::Now().ToString();

	// Build the message
	std::string strBuildMsg = "";

	strBuildMsg = std::string("[") + LogTypeArray[eLogType] + "]"
		+ "[" + std::to_string(strProcessID) + "]"
		+ "[" + std::to_string(strThreadID) + "]"
		+ "[" + strFileName + "]"
		+ "[" + std::to_string(iLineNo) + "]"
		+ "[" + strFuncName + "]"
		+ "[" + strMessage + "]"
		+ "[" + strRemark + "]"
		+ "[" + strCurTime + "]"
		+ "\r\n";

	return strBuildMsg;
}

// Add log message into container
Log::None Log::Add(std::string& strMessage)
{
	m_LogMesaageTable.push(strMessage);
}

// Connect all std::string together 
std::string Log::ConnectAllMessages()
{
	std::string strMessage = "";

	for (Index iIndex = 0; iIndex < (Index)(m_LogMesaageTable.size()); ++iIndex)
	{
		std::string strLogMsg = m_LogMesaageTable.front();

		m_LogMesaageTable.pop();

		strMessage = strMessage + strLogMsg;
	}

	return strMessage;
}

// Clear the log message container
Log::None Log::Clear()
{
	while (!m_LogMesaageTable.empty())
	{
		m_LogMesaageTable.pop();
	}

	LogMessageTable().swap(m_LogMesaageTable);
}

// Config the log environment
Log::None Log::ConfigureEnvironment(std::string strLogDirectory)
{
	if (strLogDirectory.empty())
	{
		std::cerr << "Please give me a correct directory for the log" << std::endl;

		return;
	}

	SetLogDirectory(strLogDirectory);
}

// Record log message
Log::None Log::Record(LogType eLogType,
	ProcessID strProcessID,
	ThreadID strThreadID,
	std::string strFileName,
	LineNumber iLineNo,
	std::string strFuncName,
	std::string strMessage,
	std::string strRemark)
{
	std::lock_guard<std::mutex> locker(*m_Mutex);

	// Rebuild the message
	std::string strBuildMessage = BuildMessage(eLogType,
		strProcessID,
		strThreadID,
		strFileName,
		iLineNo,
		strFuncName,
		strMessage,
		strRemark);

	// Judge the current message's number
	int iLogTableSize = static_cast<int>(m_LogMesaageTable.size());

	if (iLogTableSize < GetLogCount())
	{
		// Log the message into buffer
		Add(strBuildMessage);

		return;
	}

	// Log the message into file
	WriteLog();

	// Log the message into buffer
	Add(strBuildMessage);
}

// Flush the log messages in container to the file
Log::None Log::Flush()
{
	std::lock_guard<std::mutex> locker(*m_Mutex);

	// Write the left messages
	WriteLog();
}

// Log the message to trace window
Log::None Log::Trace(LogType eLogType,
	ProcessID strProcessID,
	ThreadID strThreadID,
	std::string strFileName,
	LineNumber iLineNo,
	std::string strFuncName,
	std::string strMessage,
	std::string strRemark)
{
	std::lock_guard<std::mutex> locker(*m_Mutex);

	// Rebuild the message
	std::string strBuildMessage = BuildMessage(eLogType,
		strProcessID,
		strThreadID,
		strFileName,
		iLineNo,
		strFuncName,
		strMessage,
		strRemark);

	std::cout << strBuildMessage << std::endl;
}

// Set log size
Log::None Log::SetMaxLoggerSize(Size iSize)
{
	SetMaxLogSize(iSize);
}

// Set record count one time
Log::None Log::SetLogCountOneTime(int iCount)
{
	SetLogCount(iCount);
}
